﻿using System;
using System.Collections.Generic;
using Qadir;

namespace Qadir.Collections.Generic
{
    /// <summary>
    /// A generic class allowing for functions to be applied in bulk to a group of objects.
    /// </summary>
    /// <typeparam name="T">A type of object to deal with.</typeparam>
    public class Manager<T> : List<T>
    {
        /// <summary>
        /// Instantiates the Manager.
        /// </summary>
        public Manager()
        {

        }
        /// <summary>
        /// Instantiates the Manager.
        /// </summary>
        /// <param name="collection">An optional IEnumerable&lt;T&gt; whose members will also be found in the Manager.</param>
        public Manager(IEnumerable<T> collection)
            :base(collection)
        {
        }

        /// <summary>
        /// Applies an Action&lt;T&gt; to all T's in the set. Alias of ApplyActionToAll.
        /// </summary>
        /// <param name="run">The Action&lt;T&gt; to invoke.</param>
        public void All(Action<T> run)
        {
            ApplyActionToAll(run);
        }

        /// <summary>
        /// Applies an ActionOnT&lt;U&gt; to every T in the set and returns a U for each iteration. The results are returned as a List&lt;U&gt;. Alias of ApplyActionToAllAndReceiveResults.
        /// </summary>
        /// <param name="run">The ActionOnT&lt;U&gt; to invoke.</param>
        public Manager<U> All<U>(QadirObject<T>.ActionOnT<U> run)
        {
            return ApplyActionToAllAndReceiveResults<U>(run);
        }

        /// <summary>
        /// Applies an Action&lt;T&gt; to all T's in the set.
        /// </summary>
        /// <param name="run">The Action&lt;T&gt; to invoke.</param>
        public void ApplyActionToAll(Action<T> run)
        {
            foreach (T t in this)
            {
                run(t);
            }
        }

        /// <summary>
        /// Applies an ActionOnT&lt;U&gt; to every T in the set and returns a U for each iteration. The results are returned as a List&lt;U&gt;.
        /// </summary>
        /// <param name="run">The ActionOnT&lt;U&gt; to invoke.</param>
        public Manager<U> ApplyActionToAllAndReceiveResults<U>(QadirObject<T>.ActionOnT<U> run)
        {
            Manager<U> result = new Manager<U>();
            foreach (T t in this)
            {
                result.Add(run(t));
            }
            return result;
        }

        /// <summary>
        /// Applies an Action&lt;T&gt; to any T. The T does not have to be contained within this Manager.
        /// </summary>
        /// <param name="run">The Action&lt;T&gt; to invoke.</param>
        /// <param name="single">A T to run the action on.</param>
        public void ApplyActionToSingle(Action<T> run, T single)
        {
            run(single);
        }

        /// <summary>
        /// Applies an ActionOnT&lt;U&gt; to any T and returns a U. The T does not have to be contained within this Manager.
        /// </summary>
        /// <param name="run">The ActionOnT&lt;U&gt; to invoke.</param>
        /// <param name="single">A T to run the action on.</param>
        public U ApplyActionToSingleAndReceiveResult<U>(QadirObject<T>.ActionOnT<U> run, T single)
        {
            return run(single);
        }

        /// <summary>
        /// Concatenates all the objects within this manager into a string.
        /// </summary>
        /// <param name="format">A format string used to add items. Ex: "I have {0}", "{0}"</param>
        /// <param name="suffix">A string appended to the result string after all items except the last. Setting suffixOnLast to true will append it after the last as well.</param>
        /// <param name="includeIndex">A boolean value determining whether to append the object's index to the resultant string when processing it.</param>
        /// <param name="incrementIndexBy">A number to increment the object's index by when outputting. Ex: A value of 1 will produce "T #1: null, ..." while a value of 2 will produce "T #0: null, ..."</param>
        /// <param name="formatParameters">Any additional parameters to format the string for each item.</param>
        /// <param name="suffixOnLast">A boolean value that indicates whether to append the suffix after the last item is processed as well.</param>
        /// <returns>Returns a concatenation of all elements in this instance, according to your options.</returns>
        public string Concatenate(string format = "{0}", object[] formatParameters = null, string suffix = "\n", bool includeIndex = false, int incrementIndexBy = 1, bool suffixOnLast = false)
        {
            string result = string.Empty;
            for (int i = 0; i < Count; i++)
            {
                if (includeIndex)
                {
                    result += string.Format("{0} #{1}: ", typeof(T).ToString(), i + incrementIndexBy);
                }
                if (formatParameters == null)
                    result += string.Format(format, this[i].ToString());
                else
                {
                    object[] formatParams = new object[formatParameters.Length + 1];
                    formatParams[0] = this[i];
                    formatParameters.CopyTo(formatParams, 1);
                    result += string.Format(format, formatParams);
                }
                if (Count - i != 1)
                    result += suffix;
            }
            return result;
        }

        /// <summary>
        /// Returns a new Manager&lt;T&gt; containing all elements of this instance satisfying the given condition.
        /// </summary>
        /// <param name="condition">A function that returns true or false for a given T arg.</param>
        /// <returns>Returns a new Manager&lt;T&gt; containing all elements of this instance satisfying the given condition.</returns>
        public Manager<T> GetElementsWhere(QadirObject<T>.EvaluateForT condition)
        {
            Manager<T> result = new Manager<T>();
            foreach (T t in this)
            {
                if (condition(t))
                    result.Add(t);
            }
            return result;
        }


        /// <summary>
        /// TODOOOOOOOOOOOOOOOOOOOOOOOOOOOOO!!!!!1
        /// </summary>
        /// <param name="query"></param>
        /// <param name="caseSensitive"></param>
        /// <returns></returns>
        public Qadir Qadir(string query, bool caseSensitive = false)
        {
            return global::Qadir.Qadir.Search(query, caseSensitive);
        }

        /// <summary>
        /// Applies an Action&lt;T&gt; to any T. The T does not have to be contained within this Manager. Alias of ApplyActionToSingle.
        /// </summary>
        /// <param name="run">The Action&lt;T&gt; to invoke.</param>
        /// <param name="single">A T to run the action on.</param>
        public void Single(Action<T> run, T single)
        {
            ApplyActionToSingle(run, single);
        }

        /// <summary>
        /// Applies an ActionOnT&lt;U&gt; to any T and returns a U. The T does not have to be contained within this Manager. Alias of ApplyActionToSingleAndReceiveResult.
        /// </summary>
        /// <param name="run">The ActionOnT&lt;U&gt; to invoke.</param>
        /// <param name="single">A T to run the action on.</param>
        public U Single<U>(QadirObject<T>.ActionOnT<U> run, T single)
        {
            return ApplyActionToSingleAndReceiveResult<U>(run, single);
        }

        /// <summary>
        /// Copies the elements of Qadir.Manager&lt;T&gt; to a new array.
        /// </summary>
        /// <returns>Returns a new T[] containing all T's in this Qadir.Manager&lt;T&gt;.</returns>
        public new T[] ToArray()
        {
            return base.ToArray();
        }

        /// <summary>
        /// Copies the elements of Qadir.Manager&lt;T&gt; satisfying the given condition to a new array.
        /// </summary>
        /// <param name="condition">A function accepting a single T as a parameter that returns true or false.</param>
        /// <returns>Returns a new T[] containing all suitable T's in this Qadir.Manager&lt;T&gt;.</returns>
        public T[] ToArrayWhere (QadirObject<T>.EvaluateForT condition)
        {
            List<T> result = new List<T>();
            foreach (T t in this)
            {
                if (condition(t))
                    result.Add(t);
            }
            return result.ToArray();
        }

        /// <summary>
        /// Generates a QadirObject instance for each member.
        /// </summary>
        /// <returns>Returns a Manager containing each member as a QadirObject.</returns>
        public Manager<QadirObject<T>> ToQadirObjects()
        {
            return All<QadirObject<T>>(foo => new QadirObject<T>(foo));
        }

        /// <summary>
        /// Returns a new Manager&lt;T&gt; containing all elements of this instance satisfying the given condition. Alias of GetElementsWhere.
        /// </summary>
        /// <param name="condition">A function that returns true or false for a given T arg.</param>
        /// <returns>Returns a new Manager&lt;T&gt; containing all elements of this instance satisfying the given condition.</returns>
        public Manager<T> Where(QadirObject<T>.EvaluateForT condition)
        {
            return GetElementsWhere(condition);
        }
    }
}
